package com.gatesim;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.gesture.Gesture;
import android.gesture.GesturePoint;
import android.gesture.GestureStroke;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;


public class GateWorld extends ImageView{
	
	private Context myContext;
	private static final int INVALID_POINTER_ID = -1;
	private int GATEWORLD_TOUCH_DIST = 15;
	private int FNTPIN_IDX = 0;
	private int TOPPIN_IDX = 1;
	private int BOTPIN_IDX = 2;
	private boolean isConnecting;
	private boolean isMoving;
	public ArrayList<Gate> gatesInUse;
	public ArrayList<Wire> wiresToDraw;
	public static boolean simulationMode;
	public static boolean animationMode;
	
	public static Resources resources;
    public static Bitmap background;
    private static Bitmap logo;
    
    //Touch variables
    private float mLastTouchX;
    private float mLastTouchY;
    private float mPosX;
    private float mPosY;
    private int mActivePointerId;
    private HashMap< String , GestureTracker > mActivePointerIDs;
    private Wire lastWire;
    private Wire[] movedGateWires;
    private Gate selectedGate;
    
    private Gesture GateWorldGesture;
    private boolean handleAsPinch;
    private boolean pinchFlag;
    private double oldDist;
    
    public Timer _gwTimer;
    private Canvas _gwCanvasRef;
    
    class GWTT extends TimerTask{
        GateWorld _gwref;
        GWTT(GateWorld g){
            super();
            _gwref = g;
        }

        @Override
        public void run() {
            ((Activity) myContext).runOnUiThread(
                new Runnable() {
                    public void run() {
                        /* stuff that updates ui */
                        _gwref.simulate();
                    }
                }
            );
            /*
            if ( _gwref != null && _gwref._gwCanvasRef != null ){
              Log.d("AnimateTimer", "calling draw.");
              _gwref.onDraw( _gwref._gwCanvasRef );
            }
            */
        }
    }
	
	public GateWorld(Context context){
		super(context);
		init(context);
	}
	
	public GateWorld(Context context,AttributeSet attrs){
		super(context,attrs);
		init(context);
	}

	public GateWorld(Context context, AttributeSet attrs, int defStyle){
		super(context, attrs, defStyle);
		init(context);
	}
	 
	 
	/**
	 * Must be called by constructor to initialize Gate and Connection
	 * with resources.
	 */
	public void init(Context context){
		myContext = context;
		resources = getResources();
		background = BitmapFactory.decodeResource(resources, R.drawable.background);
		logo =  BitmapFactory.decodeResource(resources, R.drawable.logo);
		 
		gatesInUse = new ArrayList<Gate>();
		wiresToDraw = new ArrayList<Wire>();
		Gate.setResources(resources);
		Connection.setResources(resources);
		lastWire = new Wire(null,null,null,null);
		movedGateWires = new Wire[3];
		isConnecting = false;
		isMoving = false;
		simulationMode = false;
		animationMode = false;
		handleAsPinch = true;
		pinchFlag = true;
	}
	 
	/**
	 * Starts simulation mode. 
	 */
	public void simulate(){
	    if(simulationMode == true){

	      Log.d("AnimateTimer", "calling simulate.");
    		for(Gate g: gatesInUse){
    			if(g.children.isEmpty()){
    					g.produceOut();
    			}
    		}
	    }
	    this.invalidate();
	}
	
	/**
	 * Turns off simulation mode
	 */
	public void endSim(){
		simulationMode = false;
		for(Gate g: gatesInUse){
			g.endSimulationMode();
		}
	}
	
	/**
	 * Deletes gates and wires so the user can start over.
	 */
	public void clearWorkspace(){
		gatesInUse.clear();
		wiresToDraw.clear();
	}
	 
	public static void animateMode(){
		animationMode = true;
	}
	
	public static void endAnimateMode(){
		animationMode = false;
	}
	
	public void onDraw (Canvas canvas){
	    _gwCanvasRef = canvas;
	    
		float textSize = 20f;
		super.onDraw(canvas);
		Paint paint = new Paint();
		canvas.drawBitmap(background, 0, 0, null);
		canvas.drawBitmap(logo, 20, 20, null);

		paint.setTextSize(textSize);
		canvas.drawText("Welcome to Circuit-Borg!", 
				logo.getWidth() + 40, logo.getHeight() + 20, paint);
		
		if(!gatesInUse.isEmpty()){
			for(Gate g: gatesInUse){
				g.draw(canvas, resources, simulationMode);
//				printAdjacency(g);
			}
		}	
		if(!wiresToDraw.isEmpty()){
			for(Wire w: wiresToDraw){
				if(w.complete)
					w.draw(canvas, resources, simulationMode);
//				printWire(w);
			}
		}
		//FOR GESTURE VIEWING PURPOSES
		if(mActivePointerIDs != null){
			for(GestureTracker gt : mActivePointerIDs.values()){
				gt.draw(canvas, resources);
			}
		}
	}
	
	
	/**
	 * Prints a wires details
	 * @param w
	 */
	private void printWire(Wire w){
		Log.d("Print wires", "Wire " + w.id);
		if(w.getGateSource() != null){
//			Log.d("Adjacency", "WireS " + w.getGateSource().id);
		}
		if(w.getGateDest() != null){
//			Log.d("Adjacency", "WireD " + w.getGateDest().id);	
		}
	}

	/**
	 * Prints a gates adjacency
	 * @param g
	 */
	private void printAdjacency(Gate g){
		Log.d("Adjacency", "Gate " + g.id + "=> " + g.fntPin.getCharge());
		if(!g.children.isEmpty()){
			for(Wire g1: g.children){
				Log.d("Adjacency", "child " + g1.getGateDest().id + " => " + g1.getDest().getCharge());
			}
		}
		if(g.parents[0] != null){
			Log.d("Adjacency", "parents[0] " + g.parents[0].getGateSource().id + " => " + g.parents[0].getDest().getCharge());
		}
		if(g.parents[1] != null){
			Log.d("Adjacency", "parents[1] " + g.parents[1].getGateSource().id + " => " + g.parents[1].getDest().getCharge());
		}
	}
	
	/**
	 * Detects if the user touches a pin. If so, a wire is created and started
	 * at that pin.
	 */
	public void checkPinsStartWire(){
		for(Gate g: gatesInUse){

			//If the touch is close to a front pin (both Gate and Volt have)
			if(Gate.distance(g.fntPin.getPoint(),mLastTouchX,mLastTouchY) <= GATEWORLD_TOUCH_DIST){
				//Select it!
        		g.fntPin.select();
        		//New Wire!
		        wiresToDraw.add(lastWire = new Wire(g.fntPin,null,g,null));
		        //Make wire look connected to front pin
		        lastWire.addLineEndOfPointVector(g.fntPin.getPoint().x, g.fntPin.getPoint().y);
		        //Pull focus to onTouch while a wire is being made
		        GateSimActivity.gestures.setEventsInterceptionEnabled(false);
		        isConnecting = true;
        	} 
        	if(g.isGate()){
        		//If the touch is close to a top pin (Gate only)
	        	if(Gate.distance(g.topPin.getPoint(),mLastTouchX,mLastTouchY) <= GATEWORLD_TOUCH_DIST){
	        		if(!g.topPin.isOccupied()){
		        		g.topPin.select();
				        wiresToDraw.add(lastWire = new Wire(null,g.topPin,null,g));
				        lastWire.addLineEndOfPointVector(g.topPin.getPoint().x, g.topPin.getPoint().y);
				        GateSimActivity.gestures.setEventsInterceptionEnabled(false);
				        isConnecting = true;
	        		}
	
	        	//If the touch is close to the bottom pin (Gate only)
	        	} else if( g.botPin != null && Gate.distance(g.botPin.getPoint(),mLastTouchX,mLastTouchY) <= GATEWORLD_TOUCH_DIST){
	        		if(!g.botPin.isOccupied()){
		        		g.botPin.select();
				        wiresToDraw.add(lastWire = new Wire(null,g.botPin,null,g));
				        lastWire.addLineEndOfPointVector(g.botPin.getPoint().x, g.botPin.getPoint().y);
				        GateSimActivity.gestures.setEventsInterceptionEnabled(false);
				        isConnecting = true;
	        		} 
	        	}
	        }
		}
	}
	
	/**
	 * Detects the touch of a pin. Decides if a wire can be completed.
	 */
	//OVERHAUL
	public void checkPinsEndWire(){
		for(Gate g: gatesInUse){
        	
			if(Gate.distance(g.fntPin.getPoint(),mLastTouchX,mLastTouchY) <= GATEWORLD_TOUCH_DIST){
        		if(lastWire.getSource() == null && lastWire.getDest() != null &&
        			lastWire.getGateDest().addParent(lastWire)){
        			
        			g.fntPin.select();
        			//Tell Gate g it has a new child
	        		g.addChild(lastWire);	        		
	        		//Finish up! Set source etc..
	        		lastWire.completeWireSource(g);
	        		//Make the wire look connected properly
	        		lastWire.addLineEndOfPointVector(g.fntPin.getPoint().x, g.fntPin.getPoint().y);
        		}
        	}
        	if(g.isGate()){
	        	if(Gate.distance(g.topPin.getPoint(),mLastTouchX,mLastTouchY) <= GATEWORLD_TOUCH_DIST){
	        		if( !g.topPin.isOccupied() && lastWire.getDest() == null &&
	        			lastWire.getSource() != null && g.addTopParent(lastWire)){
	        			
	        			g.topPin.select();
	        			lastWire.getGateSource().addChild(lastWire);
		        		lastWire.completeWireDest(g, g.topPin);
		        		lastWire.addLineEndOfPointVector(g.topPin.getPoint().x, g.topPin.getPoint().y);
	        		} 
	        	}else if( g.botPin != null && Gate.distance(g.botPin.getPoint(),mLastTouchX,mLastTouchY) <= GATEWORLD_TOUCH_DIST){
	        		if( !g.botPin.isOccupied() && lastWire.getDest() == null && 
		        		lastWire.getSource() != null && g.addBotParent(lastWire)){
	        			
	        			g.botPin.select();
	        			lastWire.getGateSource().addChild(lastWire);
		        		lastWire.completeWireDest(g, g.botPin);
		        		lastWire.addLineEndOfPointVector(g.botPin.getPoint().x, g.botPin.getPoint().y);
	        		} 
	        	}
	        }
		}
	}
	
	 public boolean getIsConnecting(){
		 return isConnecting;
	 }
	 
	 public boolean getIsMoving(){
		 return isMoving;
	 }
	 
	 public boolean isHandledAsPinch(){
		 return handleAsPinch;
	 }
	
    private Gate checkBounds( float tX, float tY ){
        if(!(gatesInUse.isEmpty())){
            for (Gate g : gatesInUse){
                if ( (Math.abs((tX - g.getCenter().x)) < GATEWORLD_TOUCH_DIST) &&
                     (Math.abs((tY - g.getCenter().y)) < GATEWORLD_TOUCH_DIST) ){
                    return g;
               }
            }
        }
        return null;
    }	 

	private Wire findWireWithDest(InputConnection pin) {
    	for(Wire w : wiresToDraw){
    		if(w.getDest() == pin){
    			return w;
    		}
    	}
    	return null;
	}
    
    private Wire findWireWithSource(OutputConnection pin) {
    	for(Wire w : wiresToDraw){
    		if(w.getSource() == pin){
    			return w;
    		}
    	}
    	return null;
	}
    
	//If the last point of the wire is closer to the pin, add points to the end of the vector
	//If the first point of the wire is closer, add points to the beginning of the vector
    private void addLinesToMovedGateWires(Gate g){
    	Wire fntWire = movedGateWires[FNTPIN_IDX];
    	Wire topWire = movedGateWires[TOPPIN_IDX];
    	Wire botWire = movedGateWires[BOTPIN_IDX];	
    	
    	if(fntWire != null && fntWire.pointVector.size() > 1){    		
    		Vector<Point> fntPV = fntWire.pointVector;
    		Log.d("Wires", "fntWire");
			if(Gate.distance(g.fntPin.getPoint(), fntPV.get(fntPV.size()-1).x, fntPV.get(fntPV.size()-1).y) 
    			< Gate.distance(g.fntPin.getPoint(), fntPV.get(0).x, fntPV.get(0).y)){
    			fntWire.addLineEndOfPointVector(g.fntPin.getPoint().x, g.fntPin.getPoint().y);
    		}else{
    			fntWire.addLineBegOfPointVector(g.fntPin.getPoint().x, g.fntPin.getPoint().y);
    		}
    	}
    	
    	if(g.isGate()){
	    	if(topWire != null && topWire.pointVector.size() > 1){
	    		Vector<Point> topPV = topWire.pointVector;
				if(Gate.distance(g.topPin.getPoint(), topPV.get(topPV.size()-1).x,topPV.get(topPV.size()-1).y) 
	    			< Gate.distance(g.topPin.getPoint(),topPV.get(0).x, topPV.get(0).y)){
	    			topWire.addLineEndOfPointVector(g.topPin.getPoint().x, g.topPin.getPoint().y);
	    		}else{
	    			topWire.addLineBegOfPointVector(g.topPin.getPoint().x, g.topPin.getPoint().y);
	    		}
	    	}
    	
	    	if(botWire != null && botWire.pointVector.size() > 1){
	    		Vector<Point> botPV = botWire.pointVector;
	    		if( g.botPin != null && Gate.distance(g.botPin.getPoint(), botPV.get(botPV.size()-1).x, botPV.get(botPV.size()-1).y) 
	    			< Gate.distance(g.botPin.getPoint(), botPV.get(0).x, botPV.get(0).y)){
	    			botWire.addLineEndOfPointVector(g.botPin.getPoint().x, g.botPin.getPoint().y);
	    		}else if (g.botPin != null){
	    			botWire.addLineBegOfPointVector(g.botPin.getPoint().x, g.botPin.getPoint().y);
	    		}else{}
	    	}
    	}
    }
    
    // Checks the distance between two points.
    private double checkDistance(MotionEvent ev){
        try{
            float x = ev.getX(0) - ev.getX(1);
            float y = ev.getY(0) - ev.getY(1);
        
            return Math.sqrt(x * x + y * y);
        }catch (Exception e){
            return -1;
        }
    }
    
	public boolean onTouchEvent (MotionEvent ev) { 
	     final int pointerCount = ev.getPointerCount();
		 final int action = ev.getAction();
		    switch(action & MotionEvent.ACTION_MASK){
			    case MotionEvent.ACTION_DOWN: {
			        /***
			         * CREATE A Gesture to pass into OnGesture Performed.
			         */
			        GateWorldGesture = new Gesture();
			        handleAsPinch = true;
			        pinchFlag = true;
			        
			        final float x = ev.getX();
			        final float y = ev.getY();
			           			        
			        //Set isConnecting to false, until you are actually connecting
					isConnecting = false;
					isMoving = false;	
					
					mLastTouchX = x;
				    mLastTouchY = y;
		        
			        Gate g = checkBounds(x, y);
			        if (g != null){
			            g.select(true,simulationMode);
			            selectedGate = g;
			            isMoving = true;
				        GateSimActivity.gestures.setEventsInterceptionEnabled(false);
				        //find wires connected to gate g
				        movedGateWires[FNTPIN_IDX] = findWireWithSource(g.fntPin);
				        if(g.isGate()){
					        movedGateWires[TOPPIN_IDX] = findWireWithDest(g.topPin);
					        if ( g.botPin != null )
					            movedGateWires[BOTPIN_IDX] = findWireWithDest(g.botPin);	
				        }
			        }
			        
			        if(!isMoving)
			        	checkPinsStartWire();
 			        
			        // Save the ID of this pointer
			        mActivePointerId = ev.getPointerId(0);
			        mActivePointerIDs = new HashMap<String, GestureTracker>(pointerCount);
			        initGestureTrackerAndGateWorldGesture(ev, mActivePointerId, "gesture"+mActivePointerId);
			        break;
			    }
	//		        
			    case MotionEvent.ACTION_POINTER_DOWN: {
			       if ( pointerCount == 2 && pinchFlag)
			           handleAsPinch = handleAsPinch && true;
			       else if ( pointerCount > 2 ) // disqualifies from being a gesture
			           handleAsPinch = false;
			       
			       pinchFlag = false;
			           
			    // Track new Gesture Stroke  
                   for(int i = 0; i < pointerCount; i++){
                      int gid = ev.getPointerId(i);
                      String key = "gesture"+gid;
                      if( mActivePointerIDs != null && !mActivePointerIDs.containsKey(key) ){
                        if (gid != -1){
                            initGestureTrackerAndGateWorldGesture(ev, gid, key);
                        }
                      }    
                   }
                   break;
			    }
			    
			    case MotionEvent.ACTION_MOVE: {
			        if(handleAsPinch && pointerCount == 2){
			            double dist = checkDistance(ev);
//			            if ( oldDist < dist +5 ){
//			            	handleAsPinch = false;
//			            }
			            oldDist = dist;
			        }
			        
                    /***
	                 * Search for existing gesture strokes based on ID, grab and assign
	                 * new Gesture Point to matching stroke.
	                 */
			    	 for(int i = 0; i < pointerCount; i++){
	                      int gid = ev.getPointerId(i);
	                      String key = "gesture"+gid;
	                      if( mActivePointerIDs.containsKey(key) ){
	                        if (gid != -1){
	                        	try{
	                        	  //TODO: pointerIndex out of range, we think that gid is invalid
	                        	  mActivePointerIDs.get(key).addGPALPoint( ev.getX(gid) , ev.getY(gid), ev.getEventTime() );
	                        	  mActivePointerIDs.get(key).refreshGS();
	                        	  
	                            }catch(Exception e){}
	                        }
	                      }                      
	                   }
			        
			        // Find the index of the active pointer and fetch its position
			        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
			        if( pointerIndex != -1){
    			        final float x = ev.getX(pointerIndex);
    			        final float y = ev.getY(pointerIndex);
    			        
    			        if(lastWire != null && !lastWire.complete){
    			        	lastWire.addLineEndOfPointVector(x, y);
    			        }
    		        
    			        mLastTouchX = x;
    			        mLastTouchY = y;
    			        
    			        if(selectedGate != null){
    			            selectedGate.updatePosition(new Point((int)x, (int)y));
							addLinesToMovedGateWires(selectedGate);    			        
						}
    			        
    			       if(!isMoving){
    			    	   if(lastWire.complete)
    			    	   		checkPinsStartWire();
    				       else
    				    	   checkPinsEndWire();
    			       }
			        }
                    break;
			    }
			    case MotionEvent.ACTION_POINTER_UP: {
			        
			        /***
			         * Complete Stroke
			         *   Remove Invalid Pointer ID from HashMap.
			         */
			        
//			            if (GateWorldGesture != null){
//			              for ( Entry<String, GestureTracker> esg : mActivePointerIDs.entrySet() ){
//	                        for(int i = 0; i < pointerCount; i++){
//	                            int gid = ev.getPointerId(i);
//	                            String key = "gesture"+gid;			                  
//	                        }
//			              }
//			            }
			        
//			            int gid = ev.getActionIndex();
//			            String key = "gesture"+gid;
			            

//			            GateWorldGesture.addStroke( mActivePointerIDs.get(key).getGS() );
			            
//				        HashMap<String, GestureTracker> tempPointerIDs =
//	                            new HashMap<String, GestureTracker>(pointerCount);
//	                    for(int i = 0; i < pointerCount; i++){
//	                        int gid = ev.getPointerId(i);
//	                        String key = "gesture"+gid;                        
//	                        if (gid != -1){
//	                            GestureTracker gt;
//	                            if( ( gt = mActivePointerIDs.get(key)) !=null ){
//	                                tempPointerIDs.put( key, gt );
//	                                mActivePointerIDs.get(key).setFound();
//	                            }
//	                        }
//	                    }
	                    
//	                    for ( GestureTracker gt : mActivePointerIDs.values() ){
//	                        if( !gt.isFound() ){
//	                            Log.d("StrokeAdd", "Adding Stroke" );
//	                            GestureStroke gs = gt.getGS();
//	                            GateWorldGesture.addStroke( gs );
//	                        }else{
//	                            Log.d("StrokeAdd", "Missed an Add" );
//	                        }
//	                    }
//	                    mActivePointerIDs = tempPointerIDs;
			            
				        break;
			    }
			    
			    case MotionEvent.ACTION_UP: {
			        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
			        if (pointerIndex != -1){
    			        final float x = ev.getX(pointerIndex);
    			        final float y = ev.getY(pointerIndex);
    			    	
    			    	mLastTouchX = x;
    				    mLastTouchY = y;
    			        
    			        //Have you landed on a pin?
    			        checkPinsEndWire();
    			        
    			        for(Gate g: gatesInUse){
    			        	g.fntPin.deselect();
    			        	if(g.isGate()){
    				        	g.topPin.deselect();
    				        	if (g.botPin != null)
    				        	    g.botPin.deselect();
    			            }
    			        	g.select(false,simulationMode);
                            selectedGate = null;
    			        }
    			        
    			        for(Wire w : movedGateWires){
                        	w = null;
                        }
    			        
    			        mActivePointerId = INVALID_POINTER_ID;
			        }
	               /***
	                * Complete Gesture:
	                *   Signal OnGesturePerformed in GateSimActivity.
	                *   Set Gesture to null.
	                */
                    int gid = ev.getActionIndex();
                    String key = "gesture"+gid;
                    
                    if( GateWorldGesture != null ){
                        for ( GestureTracker gt : mActivePointerIDs.values() ){
                            GateWorldGesture.addStroke( gt.getGS() );
                        }
				        
	                    if( GateWorldGesture.getStrokesCount() > 1 ){
				          if (handleAsPinch && GateWorldGesture.getStrokesCount() == 2){
				            // Execute As Pinch
				            ((GateSimActivity)myContext).handlePinch(GateWorldGesture);
				          }else{
				            ((GateSimActivity)myContext).onGesturePerformed(GateSimActivity.gestures, GateWorldGesture);
				            
				          }
	                    }
                    }
			        
			        //TODO: Decide if we want these
			        GateWorldGesture = null;
			        mActivePointerIDs = null;
                    break;
			    }			  
		    }
		this.invalidate();
		return true;
	}

	private void initGestureTrackerAndGateWorldGesture(MotionEvent ev, int gid,
			String key) {
		try{
		mActivePointerIDs.put(key, new GestureTracker(gid) );
		
		mActivePointerIDs.get(key).addGPALPoint(ev.getX(gid) , ev.getY(gid), ev.getEventTime());
		 /***
		  * Add a GestureStroke to Existing Gesture.  Assign ID to Stroke.
		  */
		
//		mActivePointerIDs.get(key).refreshGS();
//		
//		  if (GateWorldGesture != null){
//		    GestureStroke gs = mActivePointerIDs.get(key).getGS();
//		    if (gs != null){
//		      GateWorldGesture.addStroke( gs );
//		    }
//		  }
		  
		}catch(Exception e){
		    
		}
	}	
}
